﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Plugins
{
    public class PluginObject
    {
        public byte[] Assembly { get; set; }

        public string Name { get; set; }
        public string Description { get; set; }
        public string Version { get; set; }
    }

    public class RunningObject
    {
        public Guid Id { get; set; }
        public Guid PluginId { get; set; }
        public PluginObject PluginObject { get; set; }
        public bool IsRun { get; set; }
        public AppDomain AppDomain { get; set; }
    }

    public static class PluginManager
    {
        public static object obj = new object();
        public static List<PluginObject> Plugins { get; set; }
        public static List<RunningObject> RunningPlugins { get; set; }
        public static void Initial()
        {
            Plugins = new List<PluginObject>();
            RunningPlugins = new List<RunningObject>();
            GetPlugins();
        }

        private static void GetPlugins()
        {
            Directory.CreateDirectory(Application.StartupPath+ "\\Plugins");
            string[] files = Directory.GetFiles(Application.StartupPath + "\\Plugins", "*.dll");
            string[] files2 = Directory.GetFiles(Application.StartupPath + "\\Plugins", "*.exe");
            files = files.Concat(files2).ToArray();
            List<Task> tasks = new List<Task>();
            foreach (var i in files)
            {
                Task task =Task.Run(() => {
                    byte[] bytes = GetAssemblyBytes(i);
                    AppDomain pluginDomain = AppDomain.CreateDomain("View");
                    var proxy = (ViewProxyObject)pluginDomain.CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, typeof(ViewProxyObject).FullName);
                    var isPass = proxy.Invoke(bytes);
                    if (isPass)
                        Plugins.Add(new PluginObject() { Name = proxy.Name, Version = proxy.Version, Assembly = bytes, Description = proxy.Description });
                    AppDomain.Unload(pluginDomain);
                });
                tasks.Add(task);
            }
            Task.WaitAll(tasks.ToArray());
        }

        public static RunningObject RunPlugin(PluginObject obj, Guid pluginId)
        {
            AppDomainSetup setup = new AppDomainSetup();
            setup.LoaderOptimization = LoaderOptimization.NotSpecified;
            AppDomain pluginDomain = AppDomain.CreateDomain(obj.Name + Guid.NewGuid(), null, setup);
            pluginDomain.ProcessExit += PluginDomain_ProcessExit;
            pluginDomain.UnhandledException += PluginDomain_UnhandledException;
            pluginDomain.SetData("data-id", Guid.NewGuid());
            var proxy = (ProxyObject)pluginDomain.CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, typeof(ProxyObject).FullName);
            Guid id = Guid.NewGuid();
            proxy.Invoke(obj.Assembly, id);
            RunningObject runningObject = new RunningObject() { Id = id, AppDomain = pluginDomain, IsRun = true, PluginObject = obj, PluginId = pluginId };
            RunningPlugins.Add(runningObject);
            return runningObject;
        }

        private static void PluginDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {

        }

        private static void PluginDomain_ProcessExit(object sender, EventArgs e)
        {

        }

        private static byte[] GetAssemblyBytes(string fileName)
        {
            if (File.Exists(fileName))
            {
                FileStream io = new FileStream(fileName, FileMode.Open);
                byte[] bytes = new byte[io.Length];
                io.Read(bytes, 0, (int)io.Length);
                io.Dispose();
                return bytes;
            }
            return null;
        }
    }

    public class ViewProxyObject : MarshalByRefObject
    {
        public string Name { get; set; }
        public string Version { get; set; }
        public string Description { get; set; }
        public bool Invoke(byte[] assembly)
        {
            Assembly a = Assembly.Load(assembly);
            Type type = a.GetTypes().ToList().FirstOrDefault(s => typeof(IPlugin.IPlugin).IsAssignableFrom(s));
            if (type == null)
                return false;
            Version = a.GetName().Version.ToString();
            var plugin = ((IPlugin.IPlugin)Activator.CreateInstance(type));
            Name = plugin.Name;
            Description = plugin.Description;
            return true;
        }
    }
    public class ProxyObject : MarshalByRefObject
    {
        public string Name { get; set; }
        public async void Invoke(byte[] assembly, Guid id)
        {
            Assembly a = Assembly.Load(assembly);
            Type type = a.GetTypes().ToList().FirstOrDefault(s => typeof(IPlugin.IPlugin).IsAssignableFrom(s));
            await Task.Run(() => { ((IPlugin.IPlugin)Activator.CreateInstance(type)).Run(); });
            AppDomain.CurrentDomain.SetData("data-id", null);
        }
    }
}
